/*
 * =====================================================================================
 *
 *       Filename:  ptmx.c
 *
 *    Description:  get exec stdout value. 
 *
 *        Version:  1.0
 *        Created:  08/14/2012 11:52:33 AM
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  YOUR NAME (), 
 *        Company:  
 *
 * =====================================================================================
 */
#define _GNU_SOURCE
#define _XOPEN_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdarg.h>
#include <string.h>

#define SHELL_COMMAND "/bin/sh"
#define MAX_LINE 100
int SHELL_EXIT_NOTIFY_FD = -1;

int writex(int fd, const void *ptr, size_t len)
{
    char *p = (char*) ptr;
    int r;

    while(len > 0) {
        r = write(fd, p, len);
        if(r > 0) {
            len -= r;
            p += r;
        } else {
            if (r < 0) {
                printf("writex: fd=%d error %d: %s\n", fd, errno, strerror(errno));
                if (errno == EINTR)
                    continue;
            } else {
                printf("writex: fd=%d disconnected\n", fd);
            }
            return -1;
        }
    }
    return 0;
}

static int create_subprocess(const char *cmd, const char *arg0, const char *arg1, pid_t *pid)
{
    char *devname;
    int ptm;

    ptm = open("/dev/ptmx", O_RDWR); // | O_NOCTTY);
    if(ptm < 0){
        printf("[ cannot open /dev/ptmx - %s ]\n",strerror(errno));
        return -1;
    }
    fcntl(ptm, F_SETFD, FD_CLOEXEC);

    if(grantpt(ptm) || unlockpt(ptm) ||
       ((devname = (char*) ptsname(ptm)) == 0)){
        printf("[ trouble with /dev/ptmx - %s ]\n", strerror(errno));
        close(ptm);
        return -1;
    }

    *pid = fork();
    if(*pid < 0) {
        printf("- fork failed: %s -\n", strerror(errno));
        close(ptm);
        return -1;
    }

    if(*pid == 0){
        int pts;

        setsid();

        pts = open(devname, O_RDWR);


        if(pts < 0) {
            fprintf(stderr, "child failed to open pseudo-term slave: %s\n", devname);
            exit(-1);
        }

        dup2(pts, 0);
        dup2(pts, 1);
        dup2(pts, 2);

        close(pts);
        close(ptm);

        // set OOM adjustment to zero
        char text[64];
        snprintf(text, sizeof text, "/proc/%d/oom_adj", getpid());
        int fd = open(text, O_WRONLY);
		if (fd < 0)
			return -1;

		fcntl( fd, F_SETFD, FD_CLOEXEC );

        if (fd >= 0) {
            write(fd, "0", 1);
            close(fd);
        } else {
			printf("%s: unable to open %s\n", __func__, text);
        }
        execl(cmd, cmd, arg0, arg1, NULL);
        fprintf(stderr, "- exec '%s' failed: %s (%d) -\n",
                cmd, strerror(errno), errno);
        exit(-1);
    } else {
        // Don't set child's OOM adjustment to zero.
        // Let the child do it itself, as sometimes the parent starts
        // running before the child has a /proc/pid/oom_adj.
        // """__func__: unable to open /proc/644/oom_adj""" seen in some logs.
        return ptm;
    }
}

static void subproc_waiter_service(int fd, void *cookie)
{
    pid_t pid = (pid_t)cookie;

    printf("entered. fd=%d of pid=%d\n", fd, pid);
    for (;;) {
        int status;
        pid_t p = waitpid(pid, &status, 0);
        if (p == pid) {
            printf("fd=%d, post waitpid(pid=%d) status=%04x\n", fd, p, status);
            if (WIFSIGNALED(status)) {
                printf("*** Killed by signal %d\n", WTERMSIG(status));
                break;
            } else if (!WIFEXITED(status)) {
                printf("*** Didn't exit!!. status %d\n", status);
                break;
            } else if (WEXITSTATUS(status) >= 0) {
                printf("*** Exit code %d\n", WEXITSTATUS(status));
                break;
            }
		}
        usleep(100000);  // poll every 0.1 sec
    }
    printf("shell exited fd=%d of pid=%d err=%d\n", fd, pid, errno);
    if (SHELL_EXIT_NOTIFY_FD >=0) {
		int res;
		res = writex(SHELL_EXIT_NOTIFY_FD, &fd, sizeof(fd));
		printf("notified shell exit via fd=%d for pid=%d res=%d errno=%d\n",
		  SHELL_EXIT_NOTIFY_FD, pid, res, errno);
    }
}

int read_buf(const int fd, char *buf)
{
	int avail = MAX_LINE;
	int ret = -1;
	char *point = buf;
	while (1) {
		ret = read(fd, buf, avail);
		if (ret < avail) 
			break;
		buf += avail;
	}
	buf = point;

	return ret;
}

int get_exec_return_stdout(const char *command, char *buf)
{
	int ret_fd;
    pid_t pid;

	ret_fd = create_subprocess(SHELL_COMMAND, "-c", command, &pid);

    subproc_waiter_service(ret_fd, (void *)pid);

	read_buf(ret_fd, buf);

	return 0;
}

int main(int argc, char *argv[])
{
	char buf[8000];
	get_exec_return_stdout("ls", buf);
	printf("%s\n", buf);

	get_exec_return_stdout("ls -la", buf);
	printf("%s\n", buf);

    return 0;
}

